﻿/*
Copyright (c) 2010 Lance Ward
This software is licensed under the MIT license, except where otherwise noted.
For complete license details, refer to license.txt
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;

namespace ADNLib
{
	public class Renderer
	{

		public Renderer()
		{

		}

		public Bitmap RenderBMP(Animation mov, Size res, bool keepLayers)
		{
			Bitmap bmp = new Bitmap(res.Width, res.Height);
			Graphics g = Graphics.FromImage(bmp);
			g.CompositingQuality = CompositingQuality.HighQuality;
			g.SmoothingMode = SmoothingMode.HighQuality;
			RenderFrame(g, mov.Layers[0].Frames[0], res, mov.Resolution, 0, false);

			return bmp;
		}

		public void RenderPNG(Animation mov, Size res, string outputDir, bool keepLayers)
		{
			if (mov != null && res.Width >= 0 && res.Height > 0 &&
				Directory.Exists(outputDir))
			{
				int maxFrames = mov.GetTotalFrames();
				List<string> frameBmps = new List<string>();

				for (int i = 0; i < mov.Layers.Count; i++)
				{
					string layerDir = Path.Combine(outputDir, "Layer_" + i.ToString());
					if (!Directory.Exists(layerDir)) { Directory.CreateDirectory(layerDir); }	//create a temp directory for them.

					for (int j = 0; j < maxFrames; j++)
					{
						Bitmap bmp = new Bitmap(res.Width, res.Height);
						Graphics g = Graphics.FromImage(bmp);
						g.CompositingQuality = CompositingQuality.HighQuality;
						g.SmoothingMode = SmoothingMode.AntiAlias;

						int index = GetFrameIndex(mov.Layers[i], j);

						if (index >= 0)
						{
							RenderFrame(g, mov.Layers[i].Frames[index], res, mov.Resolution, j, false);
						}
						else { g.Clear(Color.Transparent); }

						string bmpPath = Path.Combine(layerDir, "F_" + j.ToString() + ".png");
						bmp.Save(bmpPath, ImageFormat.Png);
						g.Dispose();
						bmp.Dispose();
						frameBmps.Add(bmpPath);
					}
				}
			}
		}

		//the desired frame to render does not map directly to the index of the frames list.
		//this function finds the actual index of the frame based on the desired frame to render.
		private int GetFrameIndex(Layer layer, int renderFrame)
		{
			for (int i = 0; i < layer.Frames.Count; i++)
			{
				if (renderFrame >= layer.Frames[i].StartFrame &&
					renderFrame <= layer.Frames[i].StartFrame + layer.Frames[i].Span - 1)
				{ return i; }
			}

			return -1;
		}

		private void RenderFrame(Graphics g, Frame frame, Size res, Size baseRes, int frameIndex, bool isGhost)
		{
			Matrix m = new Matrix();

			//TODO: create matrix from resolution 
			//Find zoom, using Math.max and set scale
			float scale = Math.Min((float)res.Height / (float)baseRes.Height, (float)res.Width / (float)baseRes.Width);
			m.Scale(scale, scale, MatrixOrder.Prepend);

			if (frame != null && m != null)
			{
				g.Clear(Color.Transparent);  //TODO: change to frame background color
				g.Transform = m;

				for (int i = 0; i < frame.XObjects.Count; i++)
				{
                    XObject obj = null;
                    if (frame.XObjects[i].Motions.Count > 0) { obj = AnimateObject(frame, frame.XObjects[i], frameIndex); }
                    else { obj = frame.XObjects[i]; }

                    if (isGhost) { obj.DrawGhost(g); }
                    else { obj.Draw(g); }
				}
			}
		}

        private XObject AnimateObject(Frame frame, XObject obj, int frameIndex)
        {
            XObject clone = (XObject)AnimIO.CloneObject(obj);

            for (int i = 0; i < clone.Motions.Count; i++)
            {
                switch (clone.Motions[i].MotionType)
                {
                    case MotionType.LineFill:   //can only be done on ungrouped objects
                        if (clone.Type != XObjectType.Shape) { break; }
                        else
                        {
                            //divide color by the total length of animation to get average change (diff per frame)
                            //multiply by frameIndex (diff to apply)
                            //add to current color
                        }
                        break;
                    case MotionType.MotionPath:
                        {
                            float percentMove = (1.0f / clone.Motions[i].Duration) * frameIndex;
                            PointF pos = clone.Motions[i].Path.GetPositionAlongPath(percentMove);

                            clone.Move(new PointF(pos.X - clone.CenterPoint.X, pos.Y - clone.CenterPoint.Y));
                        }
                        break;
                    case MotionType.Size:
                        break;
                    case MotionType.Order:
                        break;
                }
            }

            return clone;
        }

		public Bitmap RenderBackground(Animation mov, Size res, int bgFrameIndex, int currLayerIndex, int currFrameIndex)
		{
            //exclude current layer IF the bg frame index NOT IN currentframe.startframe-span 
            //AND the currFrameIndex frame doesn't have any animations

            //this may need to be moved to a separate thread and loaded asynchronously

            int maxFrames = mov.GetTotalFrames();
            if (bgFrameIndex > maxFrames) { return new Bitmap(1, 1); }

            Bitmap bmp = new Bitmap(res.Width, res.Height);
            Graphics g = Graphics.FromImage(bmp);
            g.CompositingQuality = CompositingQuality.HighSpeed;    //no need for high quality for onion skins
            g.SmoothingMode = SmoothingMode.HighSpeed;

            for (int i = 0; i < mov.Layers.Count; i++)
            {
                int index = GetFrameIndex(mov.Layers[i], bgFrameIndex);     //the index does not map to the item index
                if (index >= 0)
                {
                    RenderFrame(g, mov.Layers[i].Frames[index], res, mov.Resolution, bgFrameIndex, false);
                }
            }

            bmp = CreateGhostImage(bmp, 0.05f);
			return bmp;
		}

		public Bitmap CreateGhostImage(Bitmap sourceBitmap, float multiplier)
		{
			if (sourceBitmap == null || (sourceBitmap.PixelFormat != PixelFormat.Format32bppArgb &&
				sourceBitmap.PixelFormat != PixelFormat.Format32bppPArgb))
			{ throw new ArgumentException("sourceBitmap cannot be null and must be a 32 pixel format"); }

			Bitmap bmp = new Bitmap(sourceBitmap);

			Rectangle bmpRect = new Rectangle(0, 0, bmp.Width, bmp.Height);
			BitmapData bmpData = bmp.LockBits(bmpRect, ImageLockMode.ReadWrite, bmp.PixelFormat);

			IntPtr firstByte = bmpData.Scan0;

			int totalBytes = bmpData.Stride * bmp.Height;

			byte[] argbVals = new byte[totalBytes];

			System.Runtime.InteropServices.Marshal.Copy(firstByte, argbVals, 0, totalBytes);

			//process bytes. every 4th byte should be the alpha
			for (int i = 3; i < argbVals.Length; i += 4)
			{
				argbVals[i] = (byte)((int)argbVals[i] * multiplier);
			}

			System.Runtime.InteropServices.Marshal.Copy(argbVals, 0, firstByte, totalBytes);

			bmp.UnlockBits(bmpData);

			return bmp;
		}

		public Image CombineImages(List<Image> images)
		{
			if (images != null && images.Count > 0)
			{
				Image baseImage = (Image)images[0].Clone();
				Graphics g = Graphics.FromImage(baseImage);

				for (int i = 1; i < images.Count; i++)
				{
					g.DrawImageUnscaled(images[i], 0, 0);
				}

				return baseImage;
			}
			else { throw new ArgumentNullException("images", "images must not be null"); }
		}
	}
}
